﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.Text;
using System.Collections.Specialized;
using System.Web;

namespace LingDong.HtmlParser
{
    public class HtmlUtility
    {
        static HtmlUtility()
        {
            InitNotHtmlEndsHashTalbe();
        }

        private static void InitNotHtmlEndsHashTalbe()
        {
            string[] notEndsWith = new string[] { 
                "()",
                "css", "js", "exe",
                "doc", "docx", "xls", "xlsx", "ppt", "pptx",
                "jpge", "gif", "jpg", "png", 
                "rm", "rmvb" };

            DicNotHtmlEnds = new Dictionary<string, bool>();
            foreach (string str in notEndsWith)
            {
                DicNotHtmlEnds[str] = true;
            }
        }

        /// <summary>
        /// Return is the link is a html link
        /// Not a complete judge
        /// </summary>
        /// <param name="link"></param>
        /// <returns></returns>
        public static bool IsHtmlLink(string link)
        {
            if (String.IsNullOrEmpty(link))
                return false;

            link = link.ToLower();
            if (link.StartsWith("javascript:"))
                return false;

            int index = link.LastIndexOf('.');
            if (index == -1)
                return true;

            string ext = link.Substring(index + 1);
            if (DicNotHtmlEnds.ContainsKey(ext))
                return false;
            return true;
        }

        /// <summary>
        /// Return if a url with "http://" is a domain
        /// </summary>
        /// <param name="url">should be a full path, starts with "http://"</param>
        /// <returns></returns>
        public static bool IsAbsolutePathDomain(string url)
        {
            if (!url.StartsWith("http://") && !url.StartsWith("https://"))
                return false;

            int count = url.Remove(url.Length - 1).Count(c => c == '/');            
            return count == 2;
        }

        /// <summary>
        /// Return domain of the url
        /// eg: http://a.b.c/d/f returns: http://a.b.c/
        /// </summary>
        /// <param name="url">should be a full path, starts with "http://"</param>
        /// <returns></returns>
        public static string GetUrlDomain(string url)
        {
            int httpCount = 0;
            if (url.StartsWith("http://"))
                httpCount = 7;
            else if (url.StartsWith("https://"))
                httpCount = 8;
            else
                return String.Empty;

            int count = url.Count(c => c == '/');
            if (count == 2)
                return url + "/";

            int lastIndex = url.IndexOf('/', httpCount + 1);
            return url.Substring(0, lastIndex + 1);
        }
        
        /// <summary>
        /// Get absolute path with "http://" by relative path
        /// </summary>
        /// <param name="link"></param>
        /// <returns></returns>
        public static string GetFullPath(string baseUrl, string link)
        {
            List<string> fullDir = GetFullDir(baseUrl);
            int fullDirCount = fullDir.Count;

            return GetFullPath(baseUrl, link, fullDir, fullDirCount);
        }

        /// <summary>
        /// Get absolute path with "http://" by relative path
        /// </summary>
        /// <param name="baseUrl"></param>
        /// <param name="link"></param>
        /// <param name="fullDir">got by GetFullDir</param>
        /// <param name="fullDirCount">got by GetFullDir</param>
        /// <returns></returns>
        public static string GetFullPath(string baseUrl, string link, List<string> fullDir, int fullDirCount)
        {
            if (String.IsNullOrEmpty(baseUrl) || String.IsNullOrEmpty(link))
                return link;

            while((link.EndsWith("#")))
              link = link.Remove(link.Length - 1);
            if (link.StartsWith(@"http://"))
                return link;
            if (link.StartsWith(@"http:\\"))
                return link.Replace(@"http:\\", @"http://");

            int linkLen = link.Length;
            if (linkLen == 0)
                return String.Empty;
            if (link[0] == '/' || link[0] == '\\')
                link = link.Substring(1);

            int dirCount = 0;
            int i = 0;
            for (i = 0; i + 2 < linkLen; i += 3)
            {
                if (link[i] == '.' && link[i + 1] == '.' &&
                    (link[i + 2] == '/' || link[i + 2] == '\\'))
                {
                    dirCount++;
                }
                else
                    break;
            }
            dirCount = Math.Min(dirCount, fullDirCount - 1);
            return fullDir[dirCount] + link.Substring(i);
        }

        /// <summary>
        /// Return a url's each level's dir
        /// Such as: "www.a.com/b/c" returns the list { "www.a.com", "www.a.com/b" }
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public static List<string> GetFullDir(string url)
        {
            if (String.IsNullOrEmpty(url))
                return new List<string>();

            List<string> fullDir = new List<string>();
            char[] splitter = new char[] { '\\', '/' };
            int index = url.LastIndexOfAny(splitter);
            while (index > 0)
            {
                fullDir.Add(url.Substring(0, index + 1));
                url = url.Substring(0, index);
                index = url.LastIndexOfAny(splitter);
            }
            fullDir.Remove("http:/");
            fullDir.Remove("http://");
            fullDir.Remove("https:/");
            fullDir.Remove("https://");
            return fullDir;
        }

        /// <summary>
        /// If the url is a domain, add '/' at the end if it doesn't has one
        /// Else does nothing
        /// </summary>
        /// <param name="fullPath">should starts with "http://"</param>
        /// <returns></returns>
        public static string GetFormattedDomain(string fullPath)
        {
            //Debug.Assert(fullPath.StartsWith("http://") || fullPath.StartsWith("https://"));

            if (IsAbsolutePathDomain(fullPath) &&
                !fullPath.EndsWith("/"))
                return fullPath + "/";
            return fullPath;
        }

        /// <summary>
        /// Encodes a url's arguments, and keep other unchanged
        /// eg: http://www.baidu.com/s?wd=测试 would be encoded to: http://www.baidu.com/s?wd=%b2%e2%ca%d4 in GB2312
        /// </summary>
        /// <param name="url">url to encode</param>
        /// <param name="encoding"></param>
        /// <returns>the encoded url</returns>
        public static string GetEncodedUrl(string url, Encoding encoding)
        {
            NameValueCollection query = HttpUtility.ParseQueryString(url, encoding);
            // not argument in the url
            if (query.Count == 0 || String.IsNullOrEmpty(query.Keys[0]))
            {
                return url;
            }

            // get values of the arguments
            List<string> argValues = new List<string>();
            int lastIndex = 0;
            foreach (string arg in query.AllKeys)
            {
                int index = url.IndexOf(arg, lastIndex);
                if (lastIndex != 0)
                {
                    argValues.Add(url.Substring(lastIndex, index - lastIndex - 1));
                }
                lastIndex = index + arg.Length + 1;
            }
            argValues.Add(url.Substring(lastIndex));

            Debug.Assert(argValues.Count == query.Count);

            // encode the arguments
            StringBuilder encodedUrl = new StringBuilder();
            int count = argValues.Count;
            for (int i = 0; i < count; i++)
            {
                encodedUrl.Append(query.Keys[i]);
                encodedUrl.Append('=');
                encodedUrl.Append(HttpUtility.UrlEncode(argValues[i], encoding));
                if (i != count - 1)
                    encodedUrl.Append('&');
            }
            return encodedUrl.ToString();
        }

        private static Dictionary<string, bool> DicNotHtmlEnds;
    }
}
